package org.hiucung.baiyun_health_report.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.httpclient.Cookie;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import org.hiucung.baiyun_health_report.api.BaiyunHealthReportApi;
import org.hiucung.baiyun_health_report.entity.BhrGenCode;
import org.hiucung.baiyun_health_report.entity.BhrStudent;
import org.hiucung.baiyun_health_report.service.BhrStudentService;
import org.hiucung.baiyun_health_report.service.SubmitHealthReportService;
import org.hiucung.baiyun_health_report.utils.Base64Utils;
import org.hiucung.baiyun_health_report.utils.HttpUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import sun.misc.BASE64Encoder;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author its-kicc
 * @desc 提交健康上班服务实现类
 * @date 2021/12/7
 */

@Slf4j
@Service
public class SubmitHealthReportServiceImpl implements SubmitHealthReportService {

    @Autowired
    private BhrStudentService bhrStudentService;


    @Override
    public void autoSubmitHealthReport() {
        /*
         * TODO 第一步 先从数据库获取（假定一次获取 50 条）学生信息，将学生信息集合，依次执行以下方法
         */

        Page<BhrStudent> pageBhrStudent = new Page<>(1,50);
        bhrStudentService.page(pageBhrStudent,null);

        List<BhrStudent> bhrStudentList = pageBhrStudent.getRecords();
        if (bhrStudentList.size() == 0){
            log.info("数据库中没有学生数据，程序执行结束");
            return;
        }
        for (BhrStudent bhrStudent : bhrStudentList) {
            pageBhrStudentAutoSubmitHealthReport(bhrStudent);
            sleepTwoMinutes();
            log.info("本轮学生健康上报结束，数据库字段已更新");
        }

    }

    /**
     * 单个学生健康上报
     * @param bhrStudent
     */
    private void pageBhrStudentAutoSubmitHealthReport(BhrStudent bhrStudent) {
        /*
         * TODO 第二步 获取验证码信息，如果获取的验证码不正确（从长度判断），需要重新获取
         */
        BhrGenCode bhrGenCode = checkBhrGenCode(getCode());

        sleepTenSeconds();

        /**
         * TODO 第三步 发起登陆请求
         */
        HttpClient httpClient = new HttpClient();

        try {
            //预先准备好需要登录的参数
            PostMethod loginPostMethod = prepareLoginParameters(bhrGenCode, bhrStudent);
            // 设置 HttpClient 接收 Cookie,用与浏览器一样的策略
            httpClient.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
            int loginStatusCode = httpClient.executeMethod(loginPostMethod);
            if (loginStatusCode != 302) {
                log.info("教务系统登录失败");
                return;
            }
            //302 重定向到新的URL
            log.info("教务系统登录成功");
            //获得登陆后的 Cookie
            StringBuffer stringBufferLoginCookies = getLoginCookie(httpClient);

            /**
             * TODO 第四步 带着Auth_token访问健康上报的表单的 set_id
             */

            //准备好发起获取健康上报表单的请求头参数
            GetMethod getOpenWjdcGetMethod = prepareWjdcParameters(stringBufferLoginCookies);
            int getOpenWjdcExecuteMethod = httpClient.executeMethod(getOpenWjdcGetMethod);
            if (getOpenWjdcExecuteMethod != 200) {
                log.info("获取响应体中的健康上报表单的 set_id 失败");
                return;
            }
            //成功之后，获取响应体中的健康上报表单的 set_id
            String healthReportFormId = getHealthReportFormId(getOpenWjdcGetMethod);

            /**
             * TODO 第五步 发起获取 上一次健康上报的数据请求 一般是昨天上报的
             */
            PostMethod getWjdcDataPostMethod = prepareGetWjdcDataParameters(bhrGenCode, healthReportFormId);
            int getWjdcDataExecuteMethod = httpClient.executeMethod(getWjdcDataPostMethod);
            if (getWjdcDataExecuteMethod != 200) {
                log.info("获取上次健康上班数据失败，http码为 : " + getWjdcDataExecuteMethod);
                return;
            }

            // 打印出返回数据，检验一下是否成功
            String lastHealthReportFormData = getWjdcDataPostMethod.getResponseBodyAsString();
            log.info("本次获取的健康上报表单信息是：" + lastHealthReportFormData);

            /**
             * TODO 第六步 分析并拼接健康上报 save请求的表单数据
             */
            NameValuePair[] healthReportData = analysisHealthReportData(healthReportFormId, lastHealthReportFormData);

            //准备好提交健康上报表单数据的请求头与表单数据
            PostMethod wjdcSavePostMethod3 = prepareWjdcSaveParameters(bhrGenCode, healthReportFormId, healthReportData);

            sleepTenSeconds();

            int wjdcSaveExecuteMethod = httpClient.executeMethod(wjdcSavePostMethod3);
            if (wjdcSaveExecuteMethod != 200){
                log.info("本次提交健康上报数据失败，http码为" + wjdcSaveExecuteMethod);
                return;
            }
            String responseBodyAsString = wjdcSavePostMethod3.getResponseBodyAsString();
            log.info("本次提交健康上报数据成功 : " + responseBodyAsString);

            //更新本次健康上报的时间
            Date date = new Date();
            SimpleDateFormat formatter =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String format = formatter.format(date);
            Date parse = formatter.parse(format);

            bhrStudent.setGmtModified(parse);
            bhrStudentService.updateById(bhrStudent);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 准备好提交健康上报表单数据的请求头与表单数据
     *
     * @param healthReportFormId
     * @param healthReportData
     * @return
     */
    private PostMethod prepareWjdcSaveParameters(BhrGenCode bhrGenCode, String healthReportFormId, NameValuePair[] healthReportData) {
        PostMethod wjdcSavePostMethod3 = new PostMethod(BaiyunHealthReportApi.postWjdcSaveUrl);

        wjdcSavePostMethod3.setRequestHeader("Cookie", bhrGenCode.getGenCodeCookie());
        wjdcSavePostMethod3.setRequestHeader("Referer", "https://byu.educationgroup.cn/wx/wxWjdc/wjdc?set_id="
                + healthReportFormId);
        wjdcSavePostMethod3.setRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36");
        wjdcSavePostMethod3.setRequestHeader("content-type", "application/x-www-form-urlencoded; charset=UTF-8");
        wjdcSavePostMethod3.setRequestBody(healthReportData);

        return wjdcSavePostMethod3;
    }

    /**
     * 分析并拼接健康上报 save请求的表单数据
     *
     * @param healthReportFormId       健康上报的表单 id
     * @param lastHealthReportFormData 上次提交健康上报的表单数据
     * @return post表单参数
     */
    private NameValuePair[] analysisHealthReportData(String healthReportFormId, String lastHealthReportFormData) {
        JSONObject jsonObject1 = JSONObject.parseObject(lastHealthReportFormData);
        JSONObject data2 = jsonObject1.getJSONObject("data");
        JSONArray nr = data2.getJSONArray("nr");

        //创建一个表单数据的集合
        List<NameValuePair> list = new ArrayList();
        list.add(new NameValuePair("set_id", healthReportFormId));

        //这个 itemData 是表单的最后一个post参数，为所有题目项的 step_id
        StringBuilder itemData = new StringBuilder();

        for (int i = 0; i < nr.size(); i++) {
            JSONObject jsonObject3 = nr.getJSONObject(i);
            //除了姓名和地址那一栏是取step_id : det_names；其他都是step_id : det_ids
            if (jsonObject3.getString("det_ids") == null) {
                NameValuePair nameValuePair = new NameValuePair(jsonObject3.getString("step_id"), jsonObject3.getString("det_names"));
                list.add(nameValuePair);
            } else {
                NameValuePair nameValuePair = new NameValuePair(jsonObject3.getString("step_id"), jsonObject3.getString("det_ids"));
                list.add(nameValuePair);
            }

            itemData.append(jsonObject3.getString("step_id"));
            //每个item之间需要用逗号间隔
            itemData.append(",");
        }
        //最后一个逗号需要删除
        String substring = itemData.substring(0, itemData.length() - 1);

        //最后把所有的含有 step_id 的数据放入集合中
        list.add(new NameValuePair("items", substring));

        //集合转数组
        NameValuePair[] healthReportData = list.toArray(new NameValuePair[list.size()]);
        return healthReportData;
    }

    /**
     * 准备 发起获取 上一次健康上报的数据请求 的请求头参数
     *
     * @param healthReportFormId
     * @return
     */
    private PostMethod prepareGetWjdcDataParameters(BhrGenCode bhrGenCode, String healthReportFormId) {
        PostMethod getWjdcDataPostMethod = new PostMethod(BaiyunHealthReportApi.getWjdcDataUrl);

        getWjdcDataPostMethod.setRequestHeader("Cookie", bhrGenCode.getGenCodeCookie());
        getWjdcDataPostMethod.setRequestHeader("Referer", "https://byu.educationgroup.cn/wx/wxWjdc/wjdc?set_id="
                + healthReportFormId);
        getWjdcDataPostMethod.setRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36");

        // 设置表单id为 Post请求的参数
        NameValuePair[] healthReportFormData = {
                new NameValuePair("set_id", healthReportFormId),
        };

        getWjdcDataPostMethod.setRequestBody(healthReportFormData);

        return getWjdcDataPostMethod;
    }

    /**
     * 获取响应体中的健康上报表单的 set_id
     *
     * @param getOpenWjdcGetMethod
     * @return
     * @throws IOException
     */
    private String getHealthReportFormId(GetMethod getOpenWjdcGetMethod) throws IOException {
        // 打印出返回数据，检验一下是否成功
        String responseBody = getOpenWjdcGetMethod.getResponseBodyAsString();
//                    log.info("获取健康上报表单相应数据：" + text);

        //最外层是数组，里边才是json数据
        JSONArray objects = JSONObject.parseArray(responseBody);
        JSONObject jsonObject = objects.getJSONObject(0);
        //健康上报表单id
        String healthReportFormId = jsonObject.getString("id");//id : CD78A79DF14667ACE0530100007FAE77
        log.info("本次获取的健康上报表单的 set_id 是：" + healthReportFormId);

        return healthReportFormId;
    }

    /**
     * 准备好健康上报表单的请求头
     *
     * @param stringBufferLoginCookies
     * @return
     */
    private GetMethod prepareWjdcParameters(StringBuffer stringBufferLoginCookies) {
        GetMethod getOpenWjdcGetMethod = new GetMethod(BaiyunHealthReportApi.getOpenWjdcSetUrl);
        // 每次访问需授权的网址时需带上前面的 cookie 作为通行证
        getOpenWjdcGetMethod.setRequestHeader("cookie", stringBufferLoginCookies.toString());
        // 你还可以通过 PostMethod/GetMethod 设置更多的请求后数据
        // 例如，referer 从哪里来的，UA 像搜索引擎都会表名自己是谁，无良搜索引擎除外
        getOpenWjdcGetMethod.setRequestHeader("Referer", "https://byu.educationgroup.cn/sso/auth/login");
        getOpenWjdcGetMethod.setRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36");

        return getOpenWjdcGetMethod;
    }

    /**
     * 获得登陆后的 Cookie
     *
     * @param httpClient
     * @return
     */
    private StringBuffer getLoginCookie(HttpClient httpClient) {
        Cookie[] loginCookies = httpClient.getState().getCookies();
        StringBuffer stringBufferLoginCookies = new StringBuffer();
        for (Cookie cookie : loginCookies) {
            stringBufferLoginCookies.append(cookie.toString() + ";");
            log.info("登陆后的cookies = " + cookie.toString());
        }
        return stringBufferLoginCookies;
    }

    /**
     * 预先准备好需要登录的参数
     *
     * @return
     */
    private PostMethod prepareLoginParameters(BhrGenCode bhrGenCode,BhrStudent bhrStudent) {
        // 模拟登陆，post
        PostMethod loginPostMethod = new PostMethod(BaiyunHealthReportApi.LoginUrl);

        // 设置登陆时post请求的参数，其中包括用户名和密码
        NameValuePair[] loginData = {
                //重定向
                new NameValuePair("redirect", "/api?scope=base&amp;response_type=code&amp;state=default&amp;redirect_uri=https%3A%2F%2Fbyu.educationgroup.cn%2Fwx%2FoauthApi%2FgetAccessToken%3Fredirect%3D%252FwxWjdc%252Fwjdc%253Fset_id%253DCD78A79DF14567ACE0530100007FAE77%26authType%3Dauth&amp;client_id=A0001"),
                //登陆类型，为学号登陆
                new NameValuePair("loginType", "account"),
                new NameValuePair("usertel", ""),
                new NameValuePair("usertel_code", ""),
                //学号
//                new NameValuePair("username", "MjAzMzE1MDMxODM="),
                new NameValuePair("username", Base64Utils.Encoder(bhrStudent.getStuNum())),
                //密码
//                new NameValuePair("password", "TGl1emh1b3NvbmcxMzM="),
                new NameValuePair("password", Base64Utils.Encoder(bhrStudent.getStuPwd())),
                //验证码
                new NameValuePair("code", bhrGenCode.getGenCode()),
        };

        //设置请求头
        loginPostMethod.setRequestHeader("sid", "1");
        loginPostMethod.setRequestHeader("surl", "byu");
        loginPostMethod.setRequestHeader("cookie", bhrGenCode.getGenCodeCookie());
        //把请求参数设置
        loginPostMethod.setRequestBody(loginData);

        return loginPostMethod;
    }

    /**
     * 获取验证码的数字，阿里云识图
     *
     * @return
     */
    private BhrGenCode getCode() {

        BhrGenCode bhrGenCode = getImageStrFromUrl();
        String baseImg = bhrGenCode.getBase64();//base64转换
        baseImg = baseImg.replaceAll("\\r\\n", "");
        String word = "";

        //阿里云看图识字的API
        String host = "https://tysbgpu.market.alicloudapi.com";
        String path = "/api/predict/ocr_general";
        String appcode = "12adbcc856a4430fbe99de3c4fcddf74";

        //请根据线上文档修改configure字段
        JSONObject configObj = new JSONObject();
        configObj.put("side", "face");
        String config_str = configObj.toString();
        //            configObj.put("min_size", 5);
        //            String config_str = "";
        String method = "POST";
        Map<String, String> headers = new HashMap<>();
        //最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359d73e9498385570ec139105
        headers.put("Authorization", "APPCODE " + appcode);

        Map<String, String> querys = new HashMap<>();

//        // 对图像进行base64编码
//        String imgBase64 = "";
//        try {
//            if (imgFile.startsWith("http")) {
//                imgBase64 = imgFile;
//            } else {
//                File file = new File(imgFile);
//                byte[] content = new byte[(int) file.length()];
//                FileInputStream finputstream = new FileInputStream(file);
//                finputstream.read(content);
//                finputstream.close();
//                imgBase64 = new String(encodeBase64(content));
//            }
//        } catch (IOException e) {
//            e.printStackTrace();
//            return "";
//        }
        // 拼装请求body的json字符串
        JSONObject requestObj = new JSONObject();
        try {
            requestObj.put("image", baseImg);
            if (config_str.length() > 0) {
                requestObj.put("configure", config_str);
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
        String bodys = requestObj.toString();

        try {
            /**
             * 重要提示如下:
             * HttpUtils请从
             * https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java
             * 下载
             *
             * 相应的依赖请参照
             * https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml
             */
            HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
            int stat = response.getStatusLine().getStatusCode();
            if (stat != 200) {
                log.info("Http code: " + stat);
                log.info("http header error msg: " + response.getFirstHeader("X-Ca-Error-Message"));
                log.info("Http body error msg:" + EntityUtils.toString(response.getEntity()));
                return bhrGenCode;
            }

            String res = EntityUtils.toString(response.getEntity());
            JSONObject res_obj = JSON.parseObject(res);

            JSONArray ret = res_obj.getJSONArray("ret");

            String success = res_obj.getString("success");
            String request_id = res_obj.getString("request_id");
            String result = ret.getJSONObject(0).getString("word");

            String regEx = "[^0-9]";
            Pattern pattern = Pattern.compile(regEx);
            Matcher matcher = pattern.matcher(result);
            word = matcher.replaceAll("");
            //识别成功之后保存到对象属性中
            bhrGenCode.setGenCode(word);
            log.info("验证码为：" + word);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return bhrGenCode;
    }

    /**
     * 获取验证码图片到本地，并取得验证码接口cookie中的loginCode
     *
     * @return
     */
    private BhrGenCode getImageStrFromUrl() {
        byte[] data = null;
        InputStream inStream = null;
        BhrGenCode bhrGenCode = new BhrGenCode();
        try {
            // 创建URL
            URL url = new URL(BaiyunHealthReportApi.imgFileUrl);
            // 创建链接
            HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
            //获取验证码为get请求
            httpURLConnection.setRequestMethod("GET");
            httpURLConnection.setConnectTimeout(5 * 1000);
            //创建传输对象
            Map<String, List<String>> headers = httpURLConnection.getHeaderFields();
            List<String> cookies = headers.get("Set-Cookie");
            if (cookies != null && cookies.size() > 0) {
                //获取图片时的Cookie
                String cookie = cookies.get(2).split(";")[0];
                log.info("数字验证码的cookie是：" + cookie);
                //将验证码的cookie放入bean中
                bhrGenCode.setGenCodeCookie(cookie);
            }
            inStream = httpURLConnection.getInputStream();
            BufferedImage src = ImageIO.read(inStream);
            //存到本地再转换成base64保存到bean中
            File file = new File("." + File.separator + "temp.jpg");
            ImageIO.write(src, "jpg", file);
            InputStream inputStream = new FileInputStream(file);
            data = new byte[inputStream.available()];
            inputStream.read(data);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                inStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // 对字节数组Base64编码
        BASE64Encoder encoder = new BASE64Encoder();
        // 返回Base64编码过的字节数组字符串
        bhrGenCode.setBase64(encoder.encode(data));
        return bhrGenCode;
    }

    /**
     * 如果获取的验证码不正确（从长度判断），需要重新获取
     *
     * @param bhrGenCode
     */
    private BhrGenCode checkBhrGenCode(BhrGenCode bhrGenCode) {
        int genCodeBeanNum = 0;

        while (bhrGenCode.getGenCode().length() <= 3) {
            BhrGenCode newBhrGenCode = getCode();

            //新对象给旧对对象赋值
            bhrGenCode.setGenCode(newBhrGenCode.getGenCode());
            bhrGenCode.setGenCodeCookie(newBhrGenCode.getGenCodeCookie());
            bhrGenCode.setBase64(newBhrGenCode.getBase64());

            genCodeBeanNum += 1;

            log.info("第" + genCodeBeanNum + "次重新执行获取验证码操作");
        }
        return bhrGenCode;
    }

    /**
     * 睡眠2分钟
     */
    private void sleepTwoMinutes() {
        try {
            TimeUnit.MINUTES.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 睡眠10秒钟
     */
    private void sleepTenSeconds() {
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
